package guice.boot.archive.container.service;

import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import guice.boot.archive.api.ArkClient;
import guice.boot.archive.common.guice.AbstractModule;
import guice.boot.archive.common.log.Logger;
import guice.boot.archive.common.log.LoggerFactory;
import guice.boot.archive.common.util.ClassLoaders;
import guice.boot.archive.common.util.OrderComparator;
import guice.boot.archive.exception.ArkRuntimeException;
import guice.boot.archive.spi.service.ArchiveService;
import guice.boot.archive.spi.service.biz.BizFactoryService;
import guice.boot.archive.spi.service.biz.BizManagerService;
import guice.boot.archive.spi.service.event.EventService;
import guice.boot.archive.spi.service.injection.InjectionService;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicBoolean;


public class Container {
    private static final Logger LOGGER = LoggerFactory.getDefaultLogger();
    private final String[] arguments;
    private AtomicBoolean started = new AtomicBoolean(false);
    private AtomicBoolean stopped = new AtomicBoolean(false);
    //
    private Injector injector;
    private List<ArchiveService> archiveServiceList = new ArrayList<>();

    public Container(String[] arguments) {
        this.arguments = arguments;
    }

    public boolean isStarted() {
        return started.get();
    }

    public boolean isRunning() {
        return isStarted() && !stopped.get();
    }

    //
    private List<AbstractModule> findModuleList() throws ArkRuntimeException {
        try {
            List<AbstractModule> moduleList = new ArrayList<>();
            for (AbstractModule module : ServiceLoader.load(AbstractModule.class)) {
                moduleList.add(module);
            }
            return moduleList;
        } catch (Throwable e) {
            throw new ArkRuntimeException(e);
        }
    }

    //
    public void start() throws ArkRuntimeException {
        if (started.compareAndSet(false, true)) {
            ClassLoader oldClassLoader = ClassLoaders.pushContextClassLoader(getClass().getClassLoader());
            try {
                LOGGER.info("Begin to start Container");

                injector = Guice.createInjector(findModuleList());
                for (Binding<ArchiveService> binding : injector
                        .findBindingsByType(new TypeLiteral<ArchiveService>() {
                        })) {
                    archiveServiceList.add(binding.getProvider().get());
                }
                Collections.sort(archiveServiceList, new OrderComparator());

                for (ArchiveService archiveService : archiveServiceList) {
                    LOGGER.info(String.format("Init Service: %s", archiveService.getClass().getName()));
                    archiveService.init();
                }

                ContainerHolder.setContainer(this);
                ArkClient.setBizFactoryService(getService(BizFactoryService.class));
                ArkClient.setBizManagerService(getService(BizManagerService.class));
                ArkClient.setInjectionService(getService(InjectionService.class));
                ArkClient.setEventService(getService(EventService.class));
                ArkClient.setArguments(arguments);
                LOGGER.info("Finish to start Container");
            } finally {
                ClassLoaders.popContextClassLoader(oldClassLoader);
            }

        }

    }

    public void stop() throws ArkRuntimeException {
        if (stopped.compareAndSet(false, true)) {
            LOGGER.info("Begin to stop Container");
            ClassLoader oldClassLoader = ClassLoaders.pushContextClassLoader(getClass().getClassLoader());
            try {
                Collections.reverse(archiveServiceList);
                for (ArchiveService archiveService : archiveServiceList) {
                    LOGGER.info(String.format("Dispose service: %s", archiveService.getClass().getName()));
                    archiveService.dispose();
                }
                LOGGER.info("Finish to stop Container");
            } finally {
                ClassLoaders.popContextClassLoader(oldClassLoader);
            }
        }
    }

    public <T> T getService(Class<T> clazz) {
        return injector.getInstance(clazz);
    }


}